import json
import os
import sys
import threading
import time
import datetime
import paho.mqtt.client as mqtt
from fastapi import FastAPI
from fastapi.requests import   Request
#from fastapi_offline import FastAPIOffline
import uvicorn
import requests
import redis
'''
api: https://eclipse.dev/paho/files/paho.mqtt.python/html/client.html
'''

def b2s( x ):
    return ('00'+hex(x)[2:])[-2:]
def bytes2str( bytes ):
    return "".join([ b2s(x) for x in bytes])

class TDtuMqtt:
    def __init__(self):
        self.settings={
            "host":"",
            "port":0,
            "uid":"",
            "pwd":"" ,
            "subTopics":[],
            "reconnectTime": 5
        }
        self.client=  mqtt.Client()
        self.clientId = ""
        '''
        self.onConnectSuccess= None
        self.onConnectFail = None
        self.onDisConnect = None
        self.onMsg=None
        '''
        self.forceClosed = False
    @staticmethod
    def encode645 (addr , option):
        res =[0x68,   0,0,0,0,0,0, 0x68 , 0x11 , 0x04 , 0,0,0,0 ]
        try:
            addr = ('000000000000'+addr)[-12:]
            offset = 1
            for i in range(6):
                ad = addr[12-i*2-2: 12-i*2 ]
                #ad = addr[i*2:i*2+2]
                adBytes = int(ad , base=16)
                res[offset] = adBytes
                offset = offset + 1
            offset = offset + 3
            size = int(0.5*len(option))
            for i in range(size):
                oi =0x33 +  int(option[6-i*2:2+6-i*2],base=10)
                #oi =0x33 +  int(option[i*2:2+i*2],base=10)
                res[offset] = oi
                offset = offset + 1
            crc = 0
            for i in range(len(res)):
                crc = crc + res[i]
                crc = crc % 256
            res.append(crc)
            res.append(0x16)
        except Exception as e:
            print(e)
        return bytearray(res)
    @staticmethod
    def decode645( bytes ):
        res={}
        success = 0
        try:
            if bytes!=None and len(bytes)>=12:
                pos =  bytes.find(0x68)
                flag =   pos>=0 and bytes[-1]==0x16
                if flag :
                    msgBytes = list(bytes[pos:])
                    bytes_addr = msgBytes[1:7]
                    bytes_item = msgBytes[-6:-4]
                    bytes_value = msgBytes[10:-7]
                    if (len( bytes_value) + 4) == msgBytes[8]:
                        bytes_addr.reverse()
                        bytes_item.reverse()
                        bytes_value.reverse()
                        addr = "".join([ ("00"+hex(x))[-2:] for x in bytes_addr])
                        value = "".join([ ("00"+hex(x))[-2:] for x in bytes_addr])
                        item = "".join([ ("00"+hex(x-0x33))[-2:] for x in bytes_addr])
                        res ={
                            "addr" : addr,
                            'item':item ,
                            "value":value ,
                            "tm":int(time.time())
                        }
                        success = 1
        except Exception as er :
            print(er)
        return success , res
    def reset(self):
        try:
            self.disconnect()
            self.connect()
        except Exception as er:
            print(er)
    def connect(self):
        res = {
            "status" :0
        }
        try:
            self.disconnect()
            self.forceClosed = False
            clientId = self.clientId if self.clientId!="" else 'mq-'+str(int(1000*time.time()))
            self.clientId = clientId
            host =  self.settings['host']
            port =  self.settings['port']
            uid =  self.settings['uid']
            pwd =  self.settings['pwd']
            #self.client=  mqtt.Client(mqtt.CallbackAPIVersion.VERSION2 , client_id= clientId)
            self.client=  mqtt.Client(client_id= clientId)
            self.client.on_connect = self.client.on_connect
            self.client.on_connect_fail = self.on_connect_fail
            self.client.on_disconnect = self.on_disconnect
            self.client.on_message = self.on_message
            self.client.username_pw_set(uid , pwd )
            code = self.client.connect(host, port, 60)
            if code ==0 :
                for topic in self.settings['subTopics']:
                    self.client.subscribe(topic)
            print(f'connect to mqtt server.{host}:{port}.[{code}]')
            self.client.loop_start()
            res['status']=1
        except Exception as er:
            print(er)
        return res
    def disconnect(self):
        res = {
            "status" :0
        }
        try:
            self.forceClosed = True
            if( self.client!=None):
                if self.client.is_connected():
                    self.disconnect()
                self.client = None
        except Exception as er:
            print(er)
        return res
    def on_connect(self , client, userdata, flags, reason_code, properties):
        try:
            print(f"on mq client connected .code:{flags} , reason:{reason_code}")
            for topic in self.settings['subTopics']:
                self.client.subscribe(topic)
        except Exception as er:
            print(er)
    def on_connect_fail(self , client, userdata):
        try:
            print('---------  connect mqtt server fail!  ---------------')
        except Exception as er:
            print(er)
    def on_disconnect(self , client, userdata, disconnect_flags, reason_code, properties):
        try:
            print('---------  disconnect from mqtt fail!  ---------------')
            print(f'disconnect_flags:{disconnect_flags}, reason_code:{reason_code}, properties:{properties}')
            print('-------------------------------------------------------')
            if not self.forceClosed:
                delay = self.settings['reconnectTime']
                time.sleep(delay)
                self.connect()
        except Exception as er:
            print(er)
    def on_message(self , client, userdata, message):
        try:
            success , info = TDtuMqtt.decode645(message.payload)
            if self.onMsg!=None:
                self.onMsg(info)
        except Exception as er:
            print(er)

class TRedisClient:
    def __init__(self):
        self.settings={
            "host":"iot.zn-power.com",
            "port":30015,
            "db":3
        }
        self.redisClient = None

    def reset(self):
        try:
            if self.redisClient!=None:
                try:
                    self.redisClient.close()
                except Exception as err:
                    print(err)
            self.redisClient = None
        except Exception as er:
            print(er)
    def writeRedisHash(self , dataPath , dictData):
        try:
            if self.redisClient==None:
                ps={
                    "host":self.settings['host'],
                    "port":self.settings['port'],
                    "db":self.settings['db']
                }
                r = redis.StrictRedis(**ps)
                if r.ping():
                    self.redisClient = r
                    print('connect redis success.')
                else:
                    print('connect redis faild.')
            if self.redisClient !=None:
                self.redisClient.hmset(dataPath , dictData)
        except Exception as er:
            print('&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&')
            print('write redis error.',er)
            print('key:' , dataPath)
            print('data:' , dictData)
            print('&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&')
class TTDBClient:
    def __init__(self):
        self.settings={
            "tdbUrl":"",
            "tdbToken":"",
            "tdbDB":""
        }
    def writePoints (self , tableName, points):
        try:
            url = self.settings['tdbUrl']
            db = self.settings['tdbDB']
            token = self.settings['tdbToken']
            repolicy = ''
            dataList=[]
            for pt in points:
                tagList = [ f"{x}={pt['tags'][x]}"  for x in pt['tags'].keys()]
                tags = ",".join(tagList)
                valueList = [ f"{x}={pt['values'][x]}"  for x in pt['values'].keys()]
                values = ",".join(valueList)
                dataList.append(f"{tableName},{tags} {values}")
            content = "\n".join(dataList)
            dataList.clear()
            dataList = None
            headers = {
                "Authorization": token,
                'Content-Type': 'application/json',
            }
            influxdb_url = f"{url}/write?db={db}"
            if repolicy != '':
                influxdb_url = influxdb_url + '&rp='+ repolicy
            resp = requests.post(url=influxdb_url, headers=headers, data=content)

            if str(resp.status_code) == '204' or str(resp.status_code) == '200':
                pass
            else:
                print(resp.content)
        except Exception as er:
            print(er)

class TDtuSvr:
    def __init__(self):
        self.apiUrl = "http://127.0.0.1:12001/znapi"
        self.settings ={}
        self.dtus={}
        self.devs={}
        self.vars={}
        self.lastTimes={
            "dtu":{},
            "var":{},
            "dev":{}
        }
        self.redisClient = TRedisClient()
        self.tdbClient = TTDBClient()
        self.mqClient = TDtuMqtt()
        self.resetFlag = False
    def encode645 (self , addr , option):
        res =[0x68,   0,0,0,0,0,0, 0x68 , 0x11 , 0x04 , 0,0,0,0 ]
        try:
            addr = ('000000000000'+addr)[-12:]
            offset = 1
            for i in range(6):
                ad = addr[12-i*2-2: 12-i*2 ]
                #ad = addr[i*2:i*2+2]
                adBytes = int(ad , base=16)
                res[offset] = adBytes
                offset = offset + 1
            offset = offset + 3
            size = int(0.5*len(option))
            for i in range(size):
                oi =0x33 +  int(option[6-i*2:2+6-i*2],base=10)
                #oi =0x33 +  int(option[i*2:2+i*2],base=10)
                res[offset] = oi
                offset = offset + 1
            crc = 0
            for i in range(len(res)):
                crc = crc + res[i]
                crc = crc % 256
            res.append(crc)
            res.append(0x16)
        except Exception as e:
            print(e)
        return bytearray(res)
    def decode645(self, bytes ):
        res={}
        success = 0
        try:
            if bytes!=None and len(bytes)>=12:
                pos =  bytes.find(0x68)
                flag =   pos>=0 and bytes[-1]==0x16
                if flag :
                    msgBytes = list(bytes[pos:])
                    bytes_addr = msgBytes[1:7]
                    bytes_item = msgBytes[-6:-4]
                    bytes_value = msgBytes[10:-7]
                    if (len( bytes_value) + 4) == msgBytes[8]:
                        bytes_addr.reverse()
                        bytes_item.reverse()
                        bytes_value.reverse()
                        addr = "".join([ ("00"+hex(x))[-2:] for x in bytes_addr])
                        value = "".join([ ("00"+hex(x))[-2:] for x in bytes_addr])
                        item = "".join([ ("00"+hex(x-0x33))[-2:] for x in bytes_addr])
                        res ={
                            "addr" : addr,
                            'item':item ,
                            "value":value ,
                            "tm":int(time.time())
                        }
                        success = 1
        except Exception as er :
            print(er)
        return success , res
    def reset(self):
        try:
            if self.redisClient!=None:
                self.redisClient.reset()
            if self.mqClient!=None:
                self.mqClient.disconnect()

            #load datas
            _url = f'{self.apiUrl}/db/query'
            ps= {
                "code":"dtu2025",
                "key": "config",
                "params":{}
            }
            respData = requests.post(  _url , json= ps).json()
            for item in respData['datasetList'][0]:
                self.settings[item['pk']] = json.loads(item['pv'])
            self.redisClient.settings= self.settings['redisInfo']
            self.mqClient.settings= self.settings['mqInfo']
            self.tdbClient.settings= self.settings['tdbInfo']
            for dtu in respData['datasetList'][1]:
                dtuId = dtu['dtuId']
                self.dtus[dtuId] = dtu
            for dev in respData['datasetList'][2]:
                devCode = dev['devCode']
                self.devs[devCode] = dev

            cmdBuffer =[]

            for var in respData['datasetList'][3]:
                varId = var['varId']
                addr  = var['devCode']
                option  = var['varItem']
                var['cmdBytes'] = self.encode645(addr ,option)
                self.vars[varId] = var
                cmdBuffer.append(var)

            cmdCount = len(cmdBuffer)
            while not self.resetFlag :
                try:
                    time.sleep(1)
                    for i in range(cmdCount):
                        if not self.resetFlag:
                            break
                        else:
                            var = cmdBuffer[i]
                            tmNow = int(time.time())
                            varTime = var['lastTime']
                            varThresholdTm = var['delayTm']
                            delayVar = tmNow - varTime
                            if delayVar >= varThresholdTm:
                                dtuId = var['dtuId']
                                dtu = self.dtus[dtuId]
                                dtuTime = dtu['lastTime']
                                delayDtu = tmNow - dtuTime
                                if delayDtu > 3 :
                                    dtu['lastTime'] = tmNow
                                    var['lastTime'] = tmNow
                                    dtuTopic = var['topicDown']
                                    self.mqClient.client.publish(dtuTopic,var['cmdBytes'], qos=0)
                                    time.sleep(0.01)
                except Exception as err:
                    print(err)
        except Exception as er:
            print(er)

app = FastAPI()
@app.get("/")
async def appRoot():
    return {
        'start app zongneng iot server 2025'
    }
@app.get("/dtu/status")
async def appRoot():
    return {
        "dtu": "dtu.dtuStatus",
        "var": "dtu.varValues"
    }
@app.get("/jsonCmd")
async def jsonCmd(req:Request ):
    res ={}
    ps= {}
    for k,v in req.query_params.items():
        ps[k] = v
    for k,v in req.path_params.items():
        ps[k] = v
    for k , v in ps.items():
        pos = k.find("$json")
        if pos>0:
            try:
                objK = k[:pos]
                res[objK] = json.loads(v)
            except Exception as eer:
                print(eer)
        else:
            res[k] = v

    return res
@app.get("/cmd")
async def cmd(req:Request ):
    res ={}
    ps= {}
    for k,v in req.query_params.items():
        ps[k] = v
    def assignPs( parent , k , v ):
        pos = k.find("$")
        if pos<0:
            parent[k] = v
        else:
            pk=k[:pos]
            sk = k[pos+1:]
            if pk not in parent.keys():
                parent[pk]={}
            np = parent[pk]
            assignPs(np , sk , v )
    for k,v in ps.items():
        assignPs(res , k , v)

    return res
if __name__=="__main__":
    print('start dtu server.')
    dtu = TDtuSvr()
    dtu.reset()
    uvicorn.run(
        "dtuserver:app" ,
        host="0.0.0.0",
        port=12003 ,
        #reload= True
    )